package com.example.security.config;
import com.example.security.config.manager.CustomAccessDecisionManager;
import com.example.security.config.manager.CustomFilterInvocationSecurityMetadataSource;
import com.example.security.service.MenuService;
import com.example.security.service.impl.MyUserDetailsServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.annotation.Jsr250Voter;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleHierarchyVoter;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

//@Configuration
//@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class XmxSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyUserDetailsServiceImpl myUserDetailsService;
    /**
     * 默认密码是会使用密码编码器进行加密的，这里我没可以先配置无需无需密码编码器加密
     * @throws Exception
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
//        return NoOpPasswordEncoder.getInstance();//无加密密码编码器
        return new BCryptPasswordEncoder();//使用强hash，迭代加密的次数为2的10次方
    }
    /**
     * 配置认证的用户信息，方式有两种
     * 一、基于内存
     *      1.后期加入新的用户，需要重启项目，修改配置，再上线，新用户才能登录
     *      2.用户修改密码，也需要重启项目，修改配置，再上线，新用户才能用新密码登录
     * 二、基于数据库
     *
     *
     * 下面角色解释
     *
     *
     * @param auth
     * @throws Exception
     *
     *
     * $2a$10$H1Iy611SSSgNae1T7CXQze6SvZmhEvfpciGJp5PqDujz4CkJ7yUUO
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        System.out.println(passwordEncoder().encode("abc123"));
        //一、基于内存
//        auth.inMemoryAuthentication()
//                .withUser("kate").password("$2a$10$jNm4Q64sw1vSUQhE48b3y.64/T2bt./g7L/xiIkJsvNar7EJOVpKy").roles("ADMIN", "USER")
//                .and()
//                .withUser("danny").password("$2a$10$H1Iy611SSSgNae1T7CXQze6SvZmhEvfpciGJp5PqDujz4CkJ7yUUO").roles("USER");
        //二、基于数据库
        auth.userDetailsService(myUserDetailsService);
    }
    /**
     *实际情况进行 角色管理,所谓角色管理就是指：不同的角色，才能访问不能接口，不具有这个角色就不能访问
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.authorizeRequests()
               ///////////////////// /////////////【一、配置授权管理方式一：基于内存】/// ///////////////////// /////////////////////
//               .antMatchers("/admin/**")
//               .hasRole("admin")
//               .antMatchers("/user/**")
//               .access("hasAnyRole('admin','user')")
//               .antMatchers("/db/**")
//               .access("hasRole('admin') and hasRole('dba')")
               ///////////////////// //////////////【一、配置授权管理方式一：基于内存】/////// ///////////////////// /////////////////

               ///////////////////// /////////////【一、配置授权管理方式一：基于数据库】/// ///////////////////// /////////////////////
               .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>
                       () {
                   @Override
                   public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                       object.setSecurityMetadataSource(cfisms());
                       ////////TODO 无需角色继承功能如下配置/////////////
//                       object.setAccessDecisionManager(cadm());
                       ////////TODO 无需角色继承功能如下配置/////////////
                       ////////TODO 将权限继承的bean加入到拦截器中/////////////
                       CustomAccessDecisionManager accessDecisionManager = new
                               CustomAccessDecisionManager(
                               new ArrayList<AccessDecisionVoter<? extends Object>>(){{
                                   add(new RoleHierarchyVoter(roleHierarchy()));
                                   add(new AuthenticatedVoter());
                                   add(new Jsr250Voter());
                               }});
                       ////////TODO 将权限继承的bean加入到拦截器中//////////
                       object.setAccessDecisionManager(accessDecisionManager);
                       return object;
                   }
               })

               ///////////////////// /////////////【一、配置授权管理方式一：基于数据库】/// ///////////////////// /////////////////////
               ///////////////////// ///////////////【二、其他任何请求都必须认证】////// ///////////////////// /////////////////////
               .anyRequest()
               .authenticated()//其他任何请求都需要认证
               .and()
               ///////////////////// ///////////////////// ///////////////////// /////////////////////

               ///////////////////// ////////////////【三、登录放行】///// ///////////////////// /////////////////////
               .formLogin()
               ///////////////////// //////////【五、自定义登录页】/////////// ///////////////////// /////////////////////
               .loginPage("/login.html")//需要通过这个接口返回一个登录页面
               ///////////////////// ///////////////////// ///////////////////// /////////////////////

               ///////////////////// //////////【六、配置登录表单元素name值，默认username,password】/////////// ///////////
               .usernameParameter("uname")
               .passwordParameter("pwd")
                ///////////////////////////////////////////////////////////////////////////////////////////////////
               ///////////////////// //////////【七、登录成功或失败返回的json】/////////// ///////////
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request,
                                                        HttpServletResponse response,
                                                        Authentication authentication) throws IOException, ServletException {
                        //1.设置响应的内容类型 json
                        response.setContentType("application/json;charset=utf-8");
                        //2.设置响应码
                        response.setStatus(200);
                        //3.构建返回数据的map
                        Map<String, Object> map = new HashMap<>();
                        map.put ("status",200);
                        Object principal = authentication.getPrincipal();//获取用户凭证(用户信息，角色信息)
                        map.put("msg",principal);
                        //4.jackson的将map转为json
                        ObjectMapper objectMapper = new ObjectMapper();
                        String json = objectMapper.writeValueAsString(map);
                        //5.通过out写出给客户端
                        PrintWriter out = response.getWriter();
                        out.write(json);
                        out.flush();
                        out.close();
                    }
                })
               .failureHandler(new AuthenticationFailureHandler(){

                   @Override
                   public void onAuthenticationFailure(HttpServletRequest request,
                                                       HttpServletResponse response,
                                                       AuthenticationException exception) throws IOException, ServletException {

                       response.setContentType("application/json;charset=utf-8");
                       PrintWriter out = response.getWriter();
                       response.setStatus(401);
                       Map<String, Object> map = new HashMap<>();
                       map.put ("status",401);
                       if (exception instanceof LockedException) {
                           map.put("msg","账户被锁定，请联系管理员!");
                       } else if (exception instanceof CredentialsExpiredException) {
                           map.put("msg","密码过期，请联系管理员!");
                       } else if (exception instanceof AccountExpiredException) {
                           map.put("msg","账户过期，请联系管理员!");
                       } else if (exception instanceof DisabledException) {
                           map.put("msg","账户被禁用，请联系管理员!");
                       } else if (exception instanceof BadCredentialsException) {
                           map.put("msg","用户名或者密码输入错误，请重新输入!");
                       }else{
                           map.put("msg","登录失败");
                       }
                       ObjectMapper om = new ObjectMapper();
                       out.write(om.writeValueAsString(map));
                       out.flush();
                       out.close();
                   }
               })
               ///////////////////// //////////【七、登录成功或失败返回的json】/////////// ///////////
               .loginProcessingUrl("/login")
               .permitAll()//表单登录的请求/login，无需认证和授权
               ///////////////////// ///////////////////// ///////////////////// /////////////////////

               ///////////////////// ////////////【八、配置登录功能】///////// ///////////////////// /////////////////////
               .and()
               .logout()//配置具有登录的功能
               .logoutUrl("/logout") //登出的url
               .clearAuthentication(true)//清除认证对象
               .invalidateHttpSession(true)//清除session
               .addLogoutHandler(new LogoutHandler() {//登录处理器
                   @Override
                   public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {

                   }
               })
               .logoutSuccessHandler(new LogoutSuccessHandler() {//登录成功后的处理
                   @Override
                   public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                       response.sendRedirect("/login.html");
                   }
               })
               ///////////////////// ////////////【八、配置登录功能】///////// ///////////////////// /////////////////////
               ///////////////////// /////////////【四、禁用csrf】//////// ///////////////////// /////////////////////
               .and()
               .csrf().disable();//开发阶段禁用防止csrf的攻击，上线阶段一定要将csrf打开
              ///////////////////// ///////////////////// ///////////////////// /////////////////////
    }

    /**
     * ROLE_admin继承ROLE_user. ROLE_dba继承 ROLE_admin,
     * @return
     */
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_dba > ROLE_admin \n ROLE_admin > ROLE_user";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }
    @Resource
    private MenuService menuService;
    @Bean
    CustomFilterInvocationSecurityMetadataSource cfisms() {
        return new CustomFilterInvocationSecurityMetadataSource(menuService);
    }

}








































